@s-hirano-ist/s-scripts 1.26.7 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +2 -0
  2. package/dist/chunk-5OXUZW2Q.js +590 -0
  3. package/dist/chunk-7PVU2Z32.js +71 -0
  4. package/dist/chunk-AJMTRCPJ.js +15 -0
  5. package/dist/chunk-AZF2R2BF.js +85 -0
  6. package/dist/chunk-F4J6JBR4.js +115 -0
  7. package/dist/chunk-HDYQV2Y2.js +58 -0
  8. package/dist/chunk-HIK7SX34.js +85 -0
  9. package/dist/chunk-IWNZQXDG.js +7660 -0
  10. package/dist/chunk-J4VLH4VA.js +267 -0
  11. package/dist/chunk-JZOR5GA2.js +42 -0
  12. package/dist/chunk-KKZP7ZF3.js +77 -0
  13. package/dist/chunk-N6TZF755.js +102 -0
  14. package/dist/chunk-S5WJ5VXW.js +85 -0
  15. package/dist/chunk-UXGLXHQ6.js +40 -0
  16. package/dist/cleanup-minio-images.js +115 -92
  17. package/dist/enrich-books.js +87 -78
  18. package/dist/fetch-articles.js +101 -90
  19. package/dist/fetch-books.js +147 -128
  20. package/dist/fetch-images.js +88 -72
  21. package/dist/fetch-notes.js +66 -54
  22. package/dist/find-duplicate-json-articles.js +100 -97
  23. package/dist/fix-book-frontmatter.js +111 -99
  24. package/dist/fix-image-formats.js +81 -65
  25. package/dist/ingest-articles.js +30 -249
  26. package/dist/ingest-books.js +61 -318
  27. package/dist/ingest-images.js +33 -184
  28. package/dist/ingest-notes.js +41 -171
  29. package/dist/rag/delete-collection.js +32 -22
  30. package/dist/rag/ingest.js +371 -92
  31. package/dist/rag/search.js +80 -76
  32. package/dist/reset-articles.js +57 -60
  33. package/dist/reset-books.js +57 -59
  34. package/dist/reset-images.js +57 -59
  35. package/dist/reset-notes.js +57 -59
  36. package/dist/revert-articles.js +53 -55
  37. package/dist/revert-books.js +53 -53
  38. package/dist/revert-images.js +53 -53
  39. package/dist/revert-notes.js +53 -53
  40. package/dist/s-content-format.js +103 -13
  41. package/dist/update-json-articles.js +101 -97
  42. package/dist/update-raw-articles.js +130 -130
  43. package/dist/validate-json-articles.js +129 -130
  44. package/package.json +14 -51
  45. package/dist/cleanup-minio-images.d.ts +0 -3
  46. package/dist/cleanup-minio-images.d.ts.map +0 -1
  47. package/dist/cleanup-minio-images.js.map +0 -1
  48. package/dist/enrich-books.d.ts +0 -3
  49. package/dist/enrich-books.d.ts.map +0 -1
  50. package/dist/enrich-books.js.map +0 -1
  51. package/dist/fetch-articles.d.ts +0 -3
  52. package/dist/fetch-articles.d.ts.map +0 -1
  53. package/dist/fetch-articles.js.map +0 -1
  54. package/dist/fetch-books.d.ts +0 -3
  55. package/dist/fetch-books.d.ts.map +0 -1
  56. package/dist/fetch-books.js.map +0 -1
  57. package/dist/fetch-images.d.ts +0 -3
  58. package/dist/fetch-images.d.ts.map +0 -1
  59. package/dist/fetch-images.js.map +0 -1
  60. package/dist/fetch-notes.d.ts +0 -3
  61. package/dist/fetch-notes.d.ts.map +0 -1
  62. package/dist/fetch-notes.js.map +0 -1
  63. package/dist/find-duplicate-json-articles.d.ts +0 -3
  64. package/dist/find-duplicate-json-articles.d.ts.map +0 -1
  65. package/dist/find-duplicate-json-articles.js.map +0 -1
  66. package/dist/fix-book-frontmatter.d.ts +0 -3
  67. package/dist/fix-book-frontmatter.d.ts.map +0 -1
  68. package/dist/fix-book-frontmatter.js.map +0 -1
  69. package/dist/fix-image-formats.d.ts +0 -3
  70. package/dist/fix-image-formats.d.ts.map +0 -1
  71. package/dist/fix-image-formats.js.map +0 -1
  72. package/dist/infrastructures/articles-command-repository.d.ts +0 -57
  73. package/dist/infrastructures/articles-command-repository.d.ts.map +0 -1
  74. package/dist/infrastructures/articles-command-repository.js +0 -68
  75. package/dist/infrastructures/articles-command-repository.js.map +0 -1
  76. package/dist/infrastructures/books-command-repository.d.ts +0 -34
  77. package/dist/infrastructures/books-command-repository.d.ts.map +0 -1
  78. package/dist/infrastructures/books-command-repository.js +0 -53
  79. package/dist/infrastructures/books-command-repository.js.map +0 -1
  80. package/dist/infrastructures/cache-invalidation-client.d.ts +0 -9
  81. package/dist/infrastructures/cache-invalidation-client.d.ts.map +0 -1
  82. package/dist/infrastructures/cache-invalidation-client.js +0 -21
  83. package/dist/infrastructures/cache-invalidation-client.js.map +0 -1
  84. package/dist/infrastructures/cache-invalidation-client.test.d.ts +0 -2
  85. package/dist/infrastructures/cache-invalidation-client.test.d.ts.map +0 -1
  86. package/dist/infrastructures/cache-invalidation-client.test.js +0 -25
  87. package/dist/infrastructures/cache-invalidation-client.test.js.map +0 -1
  88. package/dist/infrastructures/images-command-repository.d.ts +0 -34
  89. package/dist/infrastructures/images-command-repository.d.ts.map +0 -1
  90. package/dist/infrastructures/images-command-repository.js +0 -53
  91. package/dist/infrastructures/images-command-repository.js.map +0 -1
  92. package/dist/infrastructures/notes-command-repository.d.ts +0 -34
  93. package/dist/infrastructures/notes-command-repository.d.ts.map +0 -1
  94. package/dist/infrastructures/notes-command-repository.js +0 -53
  95. package/dist/infrastructures/notes-command-repository.js.map +0 -1
  96. package/dist/ingest-articles.d.ts +0 -3
  97. package/dist/ingest-articles.d.ts.map +0 -1
  98. package/dist/ingest-articles.js.map +0 -1
  99. package/dist/ingest-books.d.ts +0 -3
  100. package/dist/ingest-books.d.ts.map +0 -1
  101. package/dist/ingest-books.js.map +0 -1
  102. package/dist/ingest-images.d.ts +0 -3
  103. package/dist/ingest-images.d.ts.map +0 -1
  104. package/dist/ingest-images.js.map +0 -1
  105. package/dist/ingest-notes.d.ts +0 -3
  106. package/dist/ingest-notes.d.ts.map +0 -1
  107. package/dist/ingest-notes.js.map +0 -1
  108. package/dist/lib/glob-paths.d.ts +0 -6
  109. package/dist/lib/glob-paths.d.ts.map +0 -1
  110. package/dist/lib/glob-paths.js +0 -9
  111. package/dist/lib/glob-paths.js.map +0 -1
  112. package/dist/lib/glob-paths.test.d.ts +0 -2
  113. package/dist/lib/glob-paths.test.d.ts.map +0 -1
  114. package/dist/lib/glob-paths.test.js +0 -30
  115. package/dist/lib/glob-paths.test.js.map +0 -1
  116. package/dist/lib/html-charset.d.ts +0 -4
  117. package/dist/lib/html-charset.d.ts.map +0 -1
  118. package/dist/lib/html-charset.js +0 -33
  119. package/dist/lib/html-charset.js.map +0 -1
  120. package/dist/lib/html-charset.test.d.ts +0 -2
  121. package/dist/lib/html-charset.test.d.ts.map +0 -1
  122. package/dist/lib/html-charset.test.js +0 -27
  123. package/dist/lib/html-charset.test.js.map +0 -1
  124. package/dist/rag/delete-collection.d.ts +0 -3
  125. package/dist/rag/delete-collection.d.ts.map +0 -1
  126. package/dist/rag/delete-collection.js.map +0 -1
  127. package/dist/rag/ingest-config.d.ts +0 -8
  128. package/dist/rag/ingest-config.d.ts.map +0 -1
  129. package/dist/rag/ingest-config.js +0 -8
  130. package/dist/rag/ingest-config.js.map +0 -1
  131. package/dist/rag/ingest.d.ts +0 -3
  132. package/dist/rag/ingest.d.ts.map +0 -1
  133. package/dist/rag/ingest.js.map +0 -1
  134. package/dist/rag/search.d.ts +0 -3
  135. package/dist/rag/search.d.ts.map +0 -1
  136. package/dist/rag/search.js.map +0 -1
  137. package/dist/reset-articles.d.ts +0 -3
  138. package/dist/reset-articles.d.ts.map +0 -1
  139. package/dist/reset-articles.js.map +0 -1
  140. package/dist/reset-books.d.ts +0 -3
  141. package/dist/reset-books.d.ts.map +0 -1
  142. package/dist/reset-books.js.map +0 -1
  143. package/dist/reset-images.d.ts +0 -3
  144. package/dist/reset-images.d.ts.map +0 -1
  145. package/dist/reset-images.js.map +0 -1
  146. package/dist/reset-notes.d.ts +0 -3
  147. package/dist/reset-notes.d.ts.map +0 -1
  148. package/dist/reset-notes.js.map +0 -1
  149. package/dist/revert-articles.d.ts +0 -3
  150. package/dist/revert-articles.d.ts.map +0 -1
  151. package/dist/revert-articles.js.map +0 -1
  152. package/dist/revert-books.d.ts +0 -3
  153. package/dist/revert-books.d.ts.map +0 -1
  154. package/dist/revert-books.js.map +0 -1
  155. package/dist/revert-images.d.ts +0 -3
  156. package/dist/revert-images.d.ts.map +0 -1
  157. package/dist/revert-images.js.map +0 -1
  158. package/dist/revert-notes.d.ts +0 -3
  159. package/dist/revert-notes.d.ts.map +0 -1
  160. package/dist/revert-notes.js.map +0 -1
  161. package/dist/s-content-format-runner.d.ts +0 -10
  162. package/dist/s-content-format-runner.d.ts.map +0 -1
  163. package/dist/s-content-format-runner.js +0 -76
  164. package/dist/s-content-format-runner.js.map +0 -1
  165. package/dist/s-content-format.d.ts +0 -3
  166. package/dist/s-content-format.d.ts.map +0 -1
  167. package/dist/s-content-format.js.map +0 -1
  168. package/dist/s-content-format.test.d.ts +0 -2
  169. package/dist/s-content-format.test.d.ts.map +0 -1
  170. package/dist/s-content-format.test.js +0 -99
  171. package/dist/s-content-format.test.js.map +0 -1
  172. package/dist/update-json-articles.d.ts +0 -3
  173. package/dist/update-json-articles.d.ts.map +0 -1
  174. package/dist/update-json-articles.js.map +0 -1
  175. package/dist/update-raw-articles.d.ts +0 -3
  176. package/dist/update-raw-articles.d.ts.map +0 -1
  177. package/dist/update-raw-articles.js.map +0 -1
  178. package/dist/validate-json-articles.d.ts +0 -3
  179. package/dist/validate-json-articles.d.ts.map +0 -1
  180. package/dist/validate-json-articles.js.map +0 -1
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  CLI scripts for s-private data operations.
4
4
 
5
+ The npm artifact bundles the required internal workspace code and generated Prisma client. Consumers install only this CLI package; internal s-private packages are not separate npm dependencies. Command names remain unchanged.
6
+
5
7
  ## Installation
6
8
 
7
9
  ```sh
@@ -0,0 +1,590 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+ import {
3
+ createWebpThumbnail,
4
+ readImageMetadata
5
+ } from "./chunk-KKZP7ZF3.js";
6
+ import {
7
+ createMinioClient,
8
+ createStorageService
9
+ } from "./chunk-F4J6JBR4.js";
10
+ import {
11
+ CreatedAt,
12
+ ExportedStatus,
13
+ Id,
14
+ LastUpdatedStatus,
15
+ UnexportedStatus,
16
+ UserId,
17
+ createPrismaClient,
18
+ import_generated,
19
+ makeId,
20
+ withTransactionRetry
21
+ } from "./chunk-IWNZQXDG.js";
22
+ import {
23
+ createPushoverService
24
+ } from "./chunk-7PVU2Z32.js";
25
+
26
+ // ../core/shared-kernel/entities/file-entity.ts
27
+ import { z } from "zod";
28
+ var Path = z.string().min(1).max(512, { message: "tooLong" }).brand();
29
+ var ContentType = z.enum(["image/jpeg", "image/png", "image/webp", "jpeg", "png", "webp"]).brand();
30
+ var FileSize = z.number().int().nonnegative().max(10 * 1024 * 1024).brand();
31
+
32
+ // ../core/shared-kernel/services/entity-factory.ts
33
+ import { ZodError } from "zod";
34
+
35
+ // ../core/shared-kernel/services/url-validation.ts
36
+ var isValidHttpUrl = (url) => {
37
+ if (!url) return true;
38
+ try {
39
+ const urlObject = new URL(url);
40
+ return urlObject.protocol === "http:" || urlObject.protocol === "https:";
41
+ } catch {
42
+ return false;
43
+ }
44
+ };
45
+
46
+ // ../core/books/entities/book-entity.ts
47
+ import { z as z2 } from "zod";
48
+ var ISBN = z2.string({ message: "required" }).min(1, { message: "required" }).transform((v) => v.replaceAll("-", "")).pipe(
49
+ z2.string().regex(/^\d{13}$/u, { message: "invalidFormat" }).brand()
50
+ );
51
+ var makeISBN = (v) => ISBN.parse(v);
52
+ var BookTitle = z2.string({ message: "required" }).min(1, { message: "required" }).max(512, { message: "tooLong" }).brand();
53
+ var GoogleSubtitle = z2.string().max(512, { message: "tooLong" }).nullable().brand();
54
+ var GoogleAuthors = z2.array(z2.string()).nullable().brand();
55
+ var GoogleDescription = z2.string().nullable().brand();
56
+ var GoogleImgSrc = z2.string().max(1024, { message: "tooLong" }).refine(isValidHttpUrl, { message: "invalidFormat" }).nullable().brand();
57
+ var GoogleHref = z2.string().max(1024, { message: "tooLong" }).refine(isValidHttpUrl, { message: "invalidFormat" }).nullable().brand();
58
+ var Rating = z2.number({ message: "required" }).int({ message: "invalidFormat" }).min(1, { message: "outOfRange" }).max(5, { message: "outOfRange" }).brand();
59
+ var Tags = z2.array(
60
+ z2.string().min(1, { message: "required" }).max(64, { message: "tooLong" })
61
+ ).brand();
62
+ var BookMarkdown = z2.string().nullable().brand();
63
+ var Base = z2.object({
64
+ id: Id,
65
+ userId: UserId,
66
+ isbn: ISBN,
67
+ title: BookTitle,
68
+ rating: Rating,
69
+ tags: Tags.optional(),
70
+ googleSubTitle: GoogleSubtitle.optional(),
71
+ googleAuthors: GoogleAuthors.optional(),
72
+ googleDescription: GoogleDescription.optional(),
73
+ googleImgSrc: GoogleImgSrc.optional(),
74
+ googleHref: GoogleHref.optional(),
75
+ imagePath: Path.optional(),
76
+ markdown: BookMarkdown.optional(),
77
+ createdAt: CreatedAt
78
+ });
79
+ var UnexportedBook = Base.extend({ status: UnexportedStatus });
80
+ var LastUpdatedBook = Base.extend({ status: LastUpdatedStatus });
81
+ var ExportedBook = Base.extend(ExportedStatus.shape);
82
+
83
+ // ../infrastructure/dist/content/sharp-image-processor.js
84
+ var THUMBNAIL_WIDTH = 192;
85
+ var THUMBNAIL_HEIGHT = 192;
86
+ async function createThumbnail(bytes, width = THUMBNAIL_WIDTH, height = THUMBNAIL_HEIGHT) {
87
+ return await createWebpThumbnail(bytes, { width, height });
88
+ }
89
+ async function getMetadata(bytes) {
90
+ return await readImageMetadata(bytes);
91
+ }
92
+ var sharpImageProcessor = {
93
+ createThumbnail,
94
+ getMetadata
95
+ };
96
+
97
+ // ../application/dist/content/models.js
98
+ import { z as z3 } from "zod";
99
+ var contentDomainSchema = z3.enum([
100
+ "articles",
101
+ "notes",
102
+ "images",
103
+ "books"
104
+ ]);
105
+ var syncDomainSchema = z3.enum([
106
+ "articles",
107
+ "notes",
108
+ "images",
109
+ "books",
110
+ "categories"
111
+ ]);
112
+ var statusSchema = z3.enum(["UNEXPORTED", "LAST_UPDATED", "EXPORTED"]);
113
+ var idSchema = z3.string().min(1).max(512);
114
+ var contentBase = {
115
+ id: idSchema,
116
+ status: statusSchema,
117
+ createdAt: z3.date(),
118
+ updatedAt: z3.date(),
119
+ exportedAt: z3.date().nullable()
120
+ };
121
+ var articleSchema = z3.object({
122
+ ...contentBase,
123
+ title: z3.string(),
124
+ url: z3.url(),
125
+ quote: z3.string().nullable(),
126
+ categoryId: idSchema,
127
+ categoryName: z3.string(),
128
+ ogImageUrl: z3.string().nullable().optional(),
129
+ ogTitle: z3.string().nullable().optional(),
130
+ ogDescription: z3.string().nullable().optional()
131
+ });
132
+ var noteSchema = z3.object({
133
+ ...contentBase,
134
+ title: z3.string(),
135
+ markdown: z3.string()
136
+ });
137
+ var imageSchema = z3.object({
138
+ ...contentBase,
139
+ path: z3.string(),
140
+ contentType: z3.enum(["image/jpeg", "image/png", "image/webp"]),
141
+ fileSize: z3.number().int().nonnegative().nullable(),
142
+ width: z3.number().int().positive().nullable(),
143
+ height: z3.number().int().positive().nullable()
144
+ });
145
+ var bookSchema = z3.object({
146
+ ...contentBase,
147
+ isbn: z3.string(),
148
+ title: z3.string(),
149
+ markdown: z3.string().nullable(),
150
+ imagePath: z3.string().nullable(),
151
+ googleSubTitle: z3.string().nullable().optional(),
152
+ googleAuthors: z3.array(z3.string()).optional(),
153
+ googleDescription: z3.string().nullable().optional(),
154
+ googleImgSrc: z3.string().nullable().optional(),
155
+ googleHref: z3.string().nullable().optional(),
156
+ rating: z3.number().int(),
157
+ tags: z3.array(z3.string())
158
+ });
159
+ var categorySchema = z3.object({ id: idSchema, name: z3.string() });
160
+
161
+ // ../application/dist/batch/import-models.js
162
+ var importSchemas = {
163
+ articles: articleSchema.pick({
164
+ title: true,
165
+ url: true,
166
+ quote: true,
167
+ categoryName: true,
168
+ ogImageUrl: true,
169
+ ogTitle: true,
170
+ ogDescription: true
171
+ }),
172
+ notes: noteSchema.pick({ title: true, markdown: true }),
173
+ images: imageSchema.pick({
174
+ path: true,
175
+ contentType: true,
176
+ fileSize: true,
177
+ width: true,
178
+ height: true
179
+ }),
180
+ books: bookSchema.pick({
181
+ isbn: true,
182
+ title: true,
183
+ markdown: true,
184
+ imagePath: true,
185
+ googleSubTitle: true,
186
+ googleAuthors: true,
187
+ googleDescription: true,
188
+ googleImgSrc: true,
189
+ googleHref: true,
190
+ rating: true,
191
+ tags: true
192
+ })
193
+ };
194
+ function importKey(values) {
195
+ if ("url" in values)
196
+ return values.url;
197
+ if ("isbn" in values)
198
+ return values.isbn;
199
+ if ("path" in values)
200
+ return values.path;
201
+ return values.title;
202
+ }
203
+ function importChange(domain, id, values) {
204
+ switch (domain) {
205
+ case "articles":
206
+ return { domain, id, values: importSchemas.articles.parse(values) };
207
+ case "notes":
208
+ return { domain, id, values: importSchemas.notes.parse(values) };
209
+ case "images":
210
+ return { domain, id, values: importSchemas.images.parse(values) };
211
+ case "books":
212
+ return { domain, id, values: importSchemas.books.parse(values) };
213
+ }
214
+ throw new Error("Unsupported content domain");
215
+ }
216
+
217
+ // ../core/articles/entities/article-entity.ts
218
+ import { z as z4 } from "zod";
219
+ var CategoryName = z4.string({ message: "required" }).trim().min(1, { message: "required" }).max(16, { message: "tooLong" }).brand();
220
+ var ArticleTitle = z4.string({ message: "required" }).min(1, { message: "required" }).max(128, { message: "tooLong" }).brand();
221
+ var Quote = z4.string().max(512, { message: "tooLong" }).nullable().optional().brand();
222
+ var Url = z4.url({ message: "invalidFormat" }).min(1, { message: "required" }).max(1024, { message: "tooLong" }).refine(isValidHttpUrl, { message: "invalidFormat" }).brand();
223
+ var OgTitle = z4.string().max(512, { message: "tooLong" }).nullable().optional().brand();
224
+ var OgDescription = z4.string().max(1024, { message: "tooLong" }).nullable().optional().brand();
225
+ var OgImageUrl = z4.string().max(1024, { message: "tooLong" }).refine(isValidHttpUrl, { message: "invalidFormat" }).nullable().optional().brand();
226
+ var Base2 = z4.object({
227
+ id: Id,
228
+ userId: UserId,
229
+ categoryId: Id,
230
+ title: ArticleTitle,
231
+ quote: Quote,
232
+ url: Url,
233
+ createdAt: CreatedAt,
234
+ ogTitle: OgTitle,
235
+ ogDescription: OgDescription,
236
+ ogImageUrl: OgImageUrl
237
+ });
238
+ var UnexportedArticle = Base2.extend({ status: UnexportedStatus });
239
+ var LastUpdatedArticle = Base2.extend({ status: LastUpdatedStatus });
240
+ var ExportedArticle = Base2.extend(ExportedStatus.shape);
241
+
242
+ // ../core/notes/entities/note-entity.ts
243
+ import { z as z5 } from "zod";
244
+ var NoteTitle = z5.string({ message: "required" }).min(1, { message: "required" }).max(64, { message: "tooLong" }).brand();
245
+ var Markdown = z5.string({ message: "required" }).min(1, { message: "required" }).brand();
246
+ var Base3 = z5.object({
247
+ id: Id,
248
+ userId: UserId,
249
+ title: NoteTitle,
250
+ markdown: Markdown,
251
+ createdAt: CreatedAt
252
+ });
253
+ var UnexportedNote = Base3.extend({ status: UnexportedStatus });
254
+ var LastUpdatedNote = Base3.extend({ status: LastUpdatedStatus });
255
+ var ExportedNote = Base3.extend(ExportedStatus.shape);
256
+
257
+ // ../application/dist/batch/validate-import.js
258
+ function validateImport(change) {
259
+ switch (change.domain) {
260
+ case "articles":
261
+ ArticleTitle.parse(change.values.title);
262
+ Url.parse(change.values.url);
263
+ CategoryName.parse(change.values.categoryName);
264
+ Quote.parse(change.values.quote);
265
+ OgTitle.parse(change.values.ogTitle);
266
+ OgDescription.parse(change.values.ogDescription);
267
+ OgImageUrl.parse(change.values.ogImageUrl);
268
+ break;
269
+ case "notes":
270
+ NoteTitle.parse(change.values.title);
271
+ Markdown.parse(change.values.markdown);
272
+ break;
273
+ case "books":
274
+ ISBN.parse(change.values.isbn);
275
+ BookTitle.parse(change.values.title);
276
+ Rating.parse(change.values.rating);
277
+ Tags.parse(change.values.tags);
278
+ break;
279
+ case "images":
280
+ Path.parse(change.values.path);
281
+ break;
282
+ }
283
+ }
284
+
285
+ // ../application/dist/error.js
286
+ var ApplicationError = class extends Error {
287
+ code;
288
+ constructor(code) {
289
+ super(code);
290
+ this.code = code;
291
+ this.name = "ApplicationError";
292
+ }
293
+ };
294
+
295
+ // ../application/dist/batch/synchronize-content.js
296
+ function preserveCover(change, item, previous) {
297
+ if (item.preserveImagePath && change.domain === "books" && previous && "imagePath" in previous.values)
298
+ return {
299
+ ...change,
300
+ values: { ...change.values, imagePath: previous.values.imagePath }
301
+ };
302
+ return change;
303
+ }
304
+ async function saveItem(owner, change, item, creating, deps) {
305
+ if (item.media && (change.domain === "images" || change.domain === "books")) {
306
+ if (!deps.media)
307
+ throw new ApplicationError("UNAVAILABLE");
308
+ await deps.media.put(change.domain, item.media.path, item.media.bytes);
309
+ }
310
+ await deps.repository.save(owner, change, creating, deps.clock.now());
311
+ }
312
+ async function prune(domain, owner, records, present, deps, options) {
313
+ let deletedCount = 0;
314
+ for (const record of records) {
315
+ if (record.status !== "EXPORTED" || present.has(record.key))
316
+ continue;
317
+ const count = options.dryRun ? 1 : await deps.repository.deleteExported(domain, owner, record.id);
318
+ if (count && !options.dryRun && domain === "images" && deps.media && !await deps.repository.isMediaReferenced(record.key))
319
+ await deps.media.removeImage(record.key);
320
+ deletedCount += count;
321
+ if (count)
322
+ options.onProgress?.({ action: "deleted", key: record.key });
323
+ }
324
+ return deletedCount;
325
+ }
326
+ async function synchronizeContent(domain, owner, source, deps, options) {
327
+ const records = await deps.repository.list(domain, owner);
328
+ const existing = new Map(records.map((record) => [record.key, record]));
329
+ const present = /* @__PURE__ */ new Set();
330
+ const summary = {
331
+ insertedCount: 0,
332
+ updatedCount: 0,
333
+ skippedCount: 0,
334
+ deletedCount: 0
335
+ };
336
+ for await (const item of source) {
337
+ let change = importChange(domain, "", item.values);
338
+ validateImport(change);
339
+ const key = importKey(change.values);
340
+ present.add(key);
341
+ const previous = existing.get(key);
342
+ change = preserveCover(change, item, previous);
343
+ if (previous && (domain === "images" || JSON.stringify(previous.values) === JSON.stringify(change.values))) {
344
+ summary.skippedCount++;
345
+ options.onProgress?.({ action: "skipped", key });
346
+ continue;
347
+ }
348
+ change = { ...change, id: previous?.id ?? makeId() };
349
+ if (!options.dryRun)
350
+ await saveItem(owner, change, item, !previous, deps);
351
+ existing.set(key, {
352
+ id: change.id,
353
+ key,
354
+ status: previous?.status ?? "EXPORTED",
355
+ values: change.values
356
+ });
357
+ if (previous)
358
+ summary.updatedCount++;
359
+ else
360
+ summary.insertedCount++;
361
+ options.onProgress?.({ action: previous ? "updated" : "inserted", key });
362
+ }
363
+ summary.deletedCount = await prune(domain, owner, records, present, deps, options);
364
+ return summary;
365
+ }
366
+
367
+ // ../infrastructure/dist/persistence-errors.js
368
+ async function withPersistenceErrors(work, duplicate = "DUPLICATE") {
369
+ try {
370
+ return await work();
371
+ } catch (error) {
372
+ if (error instanceof import_generated.Prisma.PrismaClientKnownRequestError) {
373
+ if (error.code === "P2002")
374
+ throw new ApplicationError(duplicate);
375
+ if (error.code === "P2025")
376
+ throw new ApplicationError("NOT_FOUND");
377
+ }
378
+ throw error;
379
+ }
380
+ }
381
+
382
+ // ../infrastructure/dist/batch/content-import-repository.js
383
+ import { z as z6 } from "zod";
384
+ async function categoryId(tx, owner, name, now) {
385
+ const existing = await tx.category.findUnique({
386
+ where: { name_userId: { name, userId: owner } },
387
+ select: { id: true }
388
+ });
389
+ if (existing)
390
+ return existing.id;
391
+ const created = await tx.category.create({
392
+ data: { id: makeId(), name, userId: owner, createdAt: now },
393
+ select: { id: true }
394
+ });
395
+ return created.id;
396
+ }
397
+ function createContentImportRepository(prisma) {
398
+ return {
399
+ async list(domain, owner) {
400
+ let rows;
401
+ switch (domain) {
402
+ case "articles":
403
+ rows = (await prisma.article.findMany({
404
+ where: { userId: owner },
405
+ include: { Category: { select: { name: true } } }
406
+ })).map(({ Category, ...row }) => Object.assign(row, { categoryName: Category.name }));
407
+ break;
408
+ case "notes":
409
+ rows = await prisma.note.findMany({ where: { userId: owner } });
410
+ break;
411
+ case "books":
412
+ rows = await prisma.book.findMany({ where: { userId: owner } });
413
+ break;
414
+ case "images":
415
+ rows = await prisma.image.findMany({ where: { userId: owner } });
416
+ break;
417
+ }
418
+ return rows.map((row) => {
419
+ const { id, status } = z6.object({ id: z6.string(), status: statusSchema }).parse(row);
420
+ const change = importChange(domain, id, row);
421
+ return {
422
+ id,
423
+ status,
424
+ key: importKey(change.values),
425
+ values: change.values
426
+ };
427
+ });
428
+ },
429
+ async save(owner, change, creating, now) {
430
+ await withPersistenceErrors(() => withTransactionRetry(() => prisma.$transaction(async (tx) => {
431
+ const where = { id: change.id, userId: owner };
432
+ const base = {
433
+ id: change.id,
434
+ userId: owner,
435
+ status: "EXPORTED",
436
+ createdAt: now,
437
+ exportedAt: now
438
+ };
439
+ switch (change.domain) {
440
+ case "articles": {
441
+ const { categoryName, ...values } = change.values;
442
+ const category = await categoryId(tx, owner, categoryName, now);
443
+ const data = { ...values, categoryId: category };
444
+ if (creating)
445
+ await tx.article.create({ data: { ...base, ...data } });
446
+ else
447
+ await tx.article.update({ where, data });
448
+ break;
449
+ }
450
+ case "notes":
451
+ if (creating)
452
+ await tx.note.create({ data: { ...base, ...change.values } });
453
+ else
454
+ await tx.note.update({ where, data: change.values });
455
+ break;
456
+ case "books":
457
+ if (creating)
458
+ await tx.book.create({ data: { ...base, ...change.values } });
459
+ else
460
+ await tx.book.update({ where, data: change.values });
461
+ break;
462
+ case "images":
463
+ if (creating)
464
+ await tx.image.create({
465
+ data: { ...base, ...change.values }
466
+ });
467
+ else
468
+ await tx.image.update({ where, data: change.values });
469
+ break;
470
+ }
471
+ })));
472
+ },
473
+ async deleteExported(domain, owner, id) {
474
+ const where = { id, userId: owner, status: "EXPORTED" };
475
+ switch (domain) {
476
+ case "articles":
477
+ return (await prisma.article.deleteMany({ where })).count;
478
+ case "notes":
479
+ return (await prisma.note.deleteMany({ where })).count;
480
+ case "books":
481
+ return (await prisma.book.deleteMany({ where })).count;
482
+ case "images":
483
+ return (await prisma.image.deleteMany({ where })).count;
484
+ }
485
+ throw new Error("Unsupported content domain");
486
+ },
487
+ async isMediaReferenced(path) {
488
+ return await prisma.image.count({ where: { path } }) > 0;
489
+ }
490
+ };
491
+ }
492
+
493
+ // ../infrastructure/dist/batch/import-media-store.js
494
+ function createImportMediaStore(storage, processor) {
495
+ return {
496
+ async put(domain, path, bytes) {
497
+ const target = storage[domain];
498
+ try {
499
+ await target.getImageOrThrow(path, false);
500
+ } catch {
501
+ await target.uploadImage(path, bytes, false);
502
+ }
503
+ await target.uploadImage(path, await processor.createThumbnail(bytes, 192, 192), true);
504
+ },
505
+ async removeImage(path) {
506
+ await storage.images.deleteImage(path, false);
507
+ await storage.images.deleteImage(path, true);
508
+ }
509
+ };
510
+ }
511
+
512
+ // ../infrastructure/dist/shared/books-storage-service.js
513
+ var ORIGINAL_BOOK_IMAGE_PATH = "books/original";
514
+ var THUMBNAIL_BOOK_IMAGE_PATH = "books/thumbnail";
515
+ function createBooksStorageService(minioClient, bucketName) {
516
+ return createStorageService(minioClient, bucketName, {
517
+ originalPrefix: ORIGINAL_BOOK_IMAGE_PATH,
518
+ thumbnailPrefix: THUMBNAIL_BOOK_IMAGE_PATH
519
+ });
520
+ }
521
+
522
+ // ../infrastructure/dist/shared/minio-storage-service.js
523
+ function createImagesStorageService(minioClient, bucketName) {
524
+ return createStorageService(minioClient, bucketName, {
525
+ originalPrefix: "images/original",
526
+ thumbnailPrefix: "images/thumbnail"
527
+ });
528
+ }
529
+
530
+ // ../infrastructure/dist/system-clock.js
531
+ var systemClock = { now: () => /* @__PURE__ */ new Date() };
532
+
533
+ // src/lib/run-content-import.ts
534
+ async function runContentImport(domain, source) {
535
+ const databaseUrl = process.env.DATABASE_URL;
536
+ const owner = process.env.USERNAME_TO_EXPORT;
537
+ if (!databaseUrl || !owner)
538
+ throw new Error("DATABASE_URL and USERNAME_TO_EXPORT are required.");
539
+ const dryRun = process.argv.includes("--dry-run");
540
+ const prisma = createPrismaClient(databaseUrl);
541
+ const notification = createPushoverService({
542
+ url: process.env.PUSHOVER_URL ?? "",
543
+ userKey: process.env.PUSHOVER_USER_KEY ?? "",
544
+ appToken: process.env.PUSHOVER_APP_TOKEN ?? ""
545
+ });
546
+ try {
547
+ const mediaDomain = domain === "images" || domain === "books";
548
+ const bucket = process.env.MINIO_BUCKET_NAME;
549
+ if (mediaDomain && !dryRun && !bucket)
550
+ throw new Error("MINIO_BUCKET_NAME is required.");
551
+ const client = mediaDomain && !dryRun ? createMinioClient() : void 0;
552
+ const media = client && bucket ? createImportMediaStore(
553
+ {
554
+ images: createImagesStorageService(client, bucket),
555
+ books: createBooksStorageService(client, bucket)
556
+ },
557
+ sharpImageProcessor
558
+ ) : void 0;
559
+ const summary = await synchronizeContent(
560
+ domain,
561
+ owner,
562
+ source(process.env.S_CONTENTS_PATH ?? process.cwd()),
563
+ {
564
+ repository: createContentImportRepository(prisma),
565
+ clock: systemClock,
566
+ media
567
+ },
568
+ {
569
+ dryRun,
570
+ onProgress: (event) => console.log(
571
+ `${dryRun ? "[dry-run] " : ""}${event.action}: ${event.key}`
572
+ )
573
+ }
574
+ );
575
+ console.log(summary);
576
+ await notification.notifyInfo(`ingest-${domain} completed`, { caller: `ingest-${domain}` }).catch((error) => console.error("\u901A\u77E5\u5931\u6557:", error));
577
+ } catch (error) {
578
+ console.error(error);
579
+ process.exitCode = 1;
580
+ await notification.notifyError(`ingest-${domain} failed`, { caller: `ingest-${domain}` }).catch((notifyError) => console.error("\u901A\u77E5\u5931\u6557:", notifyError));
581
+ } finally {
582
+ await prisma.$disconnect();
583
+ }
584
+ }
585
+
586
+ export {
587
+ makeISBN,
588
+ sharpImageProcessor,
589
+ runContentImport
590
+ };
@@ -0,0 +1,71 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+
3
+ // ../notification/dist/errors.js
4
+ var NotificationError = class extends Error {
5
+ constructor(message = "notificationSend") {
6
+ super(message);
7
+ this.name = "NotificationError";
8
+ }
9
+ };
10
+
11
+ // ../notification/dist/pushover.js
12
+ var PushoverService = class {
13
+ config;
14
+ constructor(config) {
15
+ this.config = config;
16
+ }
17
+ /**
18
+ * Sends a message to the Pushover API.
19
+ *
20
+ * @param message - The message to send
21
+ * @param priority - The priority level (-1 = low, 0 = normal, 1 = high)
22
+ *
23
+ * @internal
24
+ */
25
+ async sendMessage(message, priority = 0) {
26
+ try {
27
+ const body = new URLSearchParams({
28
+ token: this.config.appToken,
29
+ user: this.config.userKey,
30
+ message,
31
+ priority: String(priority)
32
+ });
33
+ const result = await fetch(this.config.url, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
36
+ body
37
+ });
38
+ if (!result.ok) {
39
+ throw new NotificationError();
40
+ }
41
+ } catch (error) {
42
+ if (error instanceof NotificationError) {
43
+ console.error(`[NotificationService] Failed to send notification: ${error.message}`);
44
+ } else {
45
+ console.error("[NotificationService] Unknown error occurred while sending notification:", error);
46
+ }
47
+ }
48
+ }
49
+ async notifyError(message, context) {
50
+ const enrichedMessage = `[ERROR] ${context.caller}: ${message}`;
51
+ await this.sendMessage(enrichedMessage, 1);
52
+ }
53
+ async notifyWarning(message, context) {
54
+ const enrichedMessage = `[WARN] ${context.caller}: ${message}`;
55
+ await this.sendMessage(enrichedMessage, 0);
56
+ }
57
+ async notifyInfo(message, context) {
58
+ const enrichedMessage = `[INFO] ${context.caller}: ${message}`;
59
+ await this.sendMessage(enrichedMessage, -1);
60
+ }
61
+ };
62
+ function createPushoverService(config) {
63
+ return new PushoverService(config);
64
+ }
65
+
66
+ // ../notification/dist/web-push.js
67
+ import webPush from "web-push";
68
+
69
+ export {
70
+ createPushoverService
71
+ };
@@ -0,0 +1,15 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+
3
+ // src/lib/glob-paths.ts
4
+ import { glob } from "node:fs/promises";
5
+ async function globPaths(pattern, options = {}) {
6
+ const paths = [];
7
+ for await (const path of glob(pattern, options)) {
8
+ paths.push(path);
9
+ }
10
+ return paths.toSorted((left, right) => left.localeCompare(right, "en"));
11
+ }
12
+
13
+ export {
14
+ globPaths
15
+ };